 | | POLIMORFISMO |
MENTAL, un lenguaje polimórfico sin sobrecarga
“Polimorfismo es la cualidad de lo que tiene o puede tener distintas formas” (Diccionario de la R.A.E.)
“Polimorfismo es la cualidad o estado de existir en o asumir diferentes formas” (Diccionario Merriam-Webster)
Concepto
Polimorfismo significa “muchas formas”. Es lo contrario de monomorfismo y se refiere, en general, a la aparición de elementos de distinto tipo en alguna expresión del lenguaje.
Si una expresión tiene significados diferentes en función de los tipos de sus componentes, se dice que la expresión polimórfica está “sobrecargada” (de semántica, se sobreentiende). Si la semántica se mantiene en todo momento, el polimorfismo es sin sobrecarga.
Tipos de expresiones polimórficas
- Las funciones polimórficas y los procedimientos polimórficos son los que se pueden evaluar con argumentos de distinto tipo, en contraposición a los procedimientos y funciones monomórficas, en los que los argumentos son de tipo fijo. Una función o procedimiento sobrecargado es el que realiza acciones distintas en función de los tipos de los argumentos.
Ejemplos:
- En PostScript −un lenguaje orientado a describir páginas de impresión de manera independiente del dispositivo)− se utilizan los operadores polimórficos (no sobrecargados):
length (longitud de un objeto).
forall (recorrer todos los elementos de un objeto).
get (leer objeto de la pila).
put (colocar objeto sobre la pila).
Estas operaciones son genéricas y actúan sobre cualquier tipo de objeto (arrays, strings, números, diccionarios, etc.).
- En el lenguaje Ada [Barnes, 2013], las llamadas funciones “genéricas” son polimórficas, aunque el polimorfismo es limitado.
- ML [Milner, 1984] es un lenguaje con funciones polimórficas y que permite desarrollar fácilmente otras funciones polimórficas.
- En el cálculo lambda [Church, 1941], que es el fundamento de los lenguajes funcionales, las funciones se pueden definir y aplicar independientemente de los tipos de los argumentos.
Un operador polimórfico es aquel que admite diferentes tipos de argumentos.
En un operador sobrecargado la semántica difiere en función de los tipos de los argumentos. Lo que se pretende con los operadores sobrecargados es doble:
- Evitar tener que definir nuevos operadores.
- Facilitar la conexión conceptual con otra operación similar, aunque distinta.
Ejemplos:
- Los operadores aritméticos de la mayoría de los lenguajes de programación.
Estos operadores están sobrecargados, porque por ejemplo el operador "+" en A+B tiene distintos significados, dependiendo de que A y B sean números enteros o reales, números complejos, vectores, secuencias o matrices. En vectores y matrices indica sumar cada una de las componentes. En secuencias puede significar concatenar.
- En el lenguaje C, el operador “&” (puntero) es polimórfico no sobrecargado, puesto que se puede aplicar a cualquier tipo de operando.
- Un identificador polimórfico es aquel que puede tener un conjunto de tipos posibles. Como consecuencia, una expresión con identificadores polimórficos puede tener también un conjunto de tipos resultante. Por ejemplo, en PostScript se puede asignar a una variable cualquier tipo de objeto.
- Una estructura polimórfica es aquella que se compone de diferentes tipos de elementos. Por ejemplo, las listas en Lisp pueden contener diferentes tipos de elementos (números, textos, etc.).
- Un símbolo polimórfico es aquel que puede aparecer en diferentes contextos. Un símbolo sobrecargado es el que tiene distintos significados dependiendo del contexto. El uso de un mismo símbolo para expresar semánticas diferentes constituye una mera inconsistencia.
Ejemplos:
- Los paréntesis se suelen utilizar con varias semánticas: para despejar ambigüedades en expresiones, para especificar los argumentos de una función o procedimiento y para definir secuencias o listas.
- En el lenguaje Ada, los paréntesis están sobrecargados, pues la expresión A(I) puede tener 3 interpretaciones:
- El I-ésimo elemento de la matriz A.
- Una llamada a la función A con argumento I.
- Una conversión explícita de la expresión I al tipo A.
- En programación orientada a objetos, el polimorfismo es un concepto fundamental, que tiene dos lecturas:
- Las diferentes sensibilidades de un objeto para recibir diferentes tipos de mensajes. Se habla de objetos polimórficos.
- La posibilidad de que un mismo mensaje enviado a diferentes objetos produzca en estos objetos una acción diferente. Por ejemplo, el mensaje "+" enviado a un objeto que es un entero significa suma, mientras que si el objeto es una secuencia podría significar concatenación.
Polimorfismo implícito y explícito
Se aplican a cabeceras de procedimientos. Cuando se definen parámetros de tipo (y, por lo tanto, en los argumentos de la llamada), se habla de polimorfismo explícito.
En el polimorfismo implícito no existe parámetro de tipo, siendo el procesador del lenguaje el encargado de “descubrir” los tipos de los argumentos.
Resolución
El polimorfismo con sobrecarga requiere un proceso adicional llamado “resolución”, que consiste en determinar la semántica a aplicar entre las diferentes posibles. Esta resolución puede tener lugar en tiempo de compilación (polimorfismo estático) o en tiempo de ejecución (polimorfismo dinámico).
La resolución puede ser un proceso relativamente simple (por ejemplo en los operadores aritméticos observando los argumentos) o más complejo. La resolución es más difícil si no existen o son opcionales las declaraciones de los identificadores.
En la resolución de la sobrecarga, Tennenbaum [1974] distingue entre:
- Resolución hacia adelante. Determina el conjunto de tipos posibles de un operador a partir de sus operandos.
- Resolución hacia atrás. Basada en el tipo previsto según el contexto.
Por ejemplo, en Ada, la resolución de la sobrecarga se realiza mediante una pasada hacia adelante y otra hacia atrás.
¿Qué tipo de polimorfismo interesa más?
El polimorfismo con sobrecarga tiene dos inconvenientes:
- Crea confusión, pues hay diferentes semánticas en juego.
- Hay que realizar un proceso de resolución, lo que consume recursos.
En cambio, el polimorfismo sin sobrecarga mantiene la semántica en todo momento y también la consistencia. Por lo tanto, sólo nos interesa el polimorfismo sin sobrecarga.
Ventajas del polimorfismo sin sobrecarga
- Facilita la especificación de algoritmos, orientándolos a su aspecto conceptual o semántico, liberando la codificación de aspectos de bajo nivel.
- Generaliza la codificación. Un mismo algoritmo puede servir para acceder a manipular diferentes estructuras de datos, independientemente de los tipo de los elementos constituyentes. Por ejemplo, una función que devuelve la longitud de una lista, que accede a un elemento de una lista, etc. independientemente del tipo de sus elementos.
- Simplifica la programación. No es necesario codificar diferentes procedimientos para cada tipo de elemento.
- Supone una relajación de las estrictas normas o reglas operativas y proporciona, por lo tanto, un mayor grado de libertad y flexibilidad.
- Las operaciones polimórficas ponen de manifiesto una esencia común, una abstracción en definitiva que es independiente de los elementos sobre los que opera.
- Proporciona un sistema combinatorio indispensable para relacionar elementos de diferente tipo, favoreciendo la creatividad.
MENTAL y el Polimorfismo
Primitivas semánticas sin sobrecarga
MENTAL es un lenguaje polimórfico sin sobrecarga. Por ejemplo, en la expresión x+y
, x
e y
pueden ser cualquier tipo de expresión, pero la semántica se mantiene. Es decir, si x
e y
son dos matrices y son iguales, el resultado es 2*x
. Y si sumamos dos expresiones iguales a 〈x+y〉
, el resultado es 2*〈x+y〉
.
Todas las primitivas semánticas son polimórficas sin sobrecarga, es decir, la semántica asociada a cada primitiva es siempre la misma. Es decir, la suma siempre es suma, la sustitución es siempre sustitución, etc. Tiene que ser así forzosamente, pues la universalidad de las primitivas así lo exige. Y además el polimorfismo está íntimamente relacionado con la libertad, con la posibilidad de formar expresiones sin restricciones. Cuando una expresión polimórfica no tiene posibilidad de evaluarse, se autoevalúa.
Ejemplos de expresiones polimórficas con las primitivas semánticas
( a/b 17.5 〈( f(x y) = x+y+7 )〉 ) // secuencia
{ a/b f(3 4) 〈( f(x y) = x+y+7 )〉 } // conjunto
( 〈( f(x y) = x+y+7 )〉 + 12 ) // suma
(x+y+z = {a b c}) // sustitución
( 〈( f(x y) = x+y+7 )〉 = 33 ) // sustitución
color/〈( f(x y) = x+y+7 )〉 // particularización
(a/b ≡ 33) // equivalencia
(33 ← a/b) // condición
(a/b 17.5 "texto")↓ // acceso al contenido de una secuencia
Operaciones derivadas y polimorfismo
Las operaciones derivadas pueden ser totalmente polimórficas o parcialmente polimórficas, dependiendo de sus definiciones. Cuando las variables son de un determinado tipo, eso indica que se exige forzosamente ese tipo para que tenga sentido. Si el tipo no se ajusta al previsto, la expresión se autoevalúa. Ejemplos:
- La operación derivada
x∪y
indica unión de las expresiones x
e y
, que deben ser del mismo tipo (secuencias o conjuntos).
abc∪175 // ev. abc175
{a b c}∪{1 7 5} // ev. {a b c 1 7 5}
- La derivada
x★n
indica repetir x n
veces. La expresión x
puede ser cualquiera, pero n
indica que tiene que ser un número natural. Si n
no es un número natural, la expresión se autoevalúa.
abc★3 // rep. (abc abc abc)
( abc★"texto" ) // se autoevalúa
- La derivada
n1…n2
indica la secuencia abierta de números entre n1
y n2
. En este caso los valores inicial y final tienen que ser números enteros o expresiones sumables.
( 1…4 ) // rep. ( 1 2 3 4 )
( abc1…abc4 ) // rep. (abc1 abc2 abc3 abc4 )
Números enteros y polimorfismo
Un número entero, por ejemplo 1234
, es una secuencia de dígitos, pues representa, de forma compacta, a la secuencia (1 2 3 4)
. SEgún la operación, se trata como número o como secuencia. Ejemplos:
1234*5 // ev. 6170 (1234 se trata como número entero)
(1234 ∪ ab) // ev. 1234ab (1234 se trata como secuencia)
Condición como existencia
Una expresión que hace de condición se interpreta como existencia. Ejemplo:
La expresión (x ← a=b)
se interpreta como x
si existe (se cumple) la igualdad a=b
.
Operadores con atributos
Con el fin de simplificar el número de operadores, se puede utilizar un mismo operador con diferentes atributos para así facilitar la conexión conceptual con el operador. Por ejemplo,
+/v
como “suma vectorial”
+/m
como “suma matricial”
*/e
como “producto escalar”
Ejemplos:
- Suma vectorial:
〈( x +/v y) =: ( [x\⌊1…x#⌋ + y\⌊1…y#⌋] ) )〉
(a = (11 22 33))
(b = (1 2 3))
(a +/v b) // ev. (11+1 + 22+2 + 33+3) ev. (12 24 36)
- Producto escalar de dos vectores:
〈( x */e y) =: +⊣( [x\⌊1…x#⌋ * y\⌊1…y#⌋] ) )〉
(a = (11 22 33))
(b = (1 2 3)
(a */e b) // ev. (11*1 + 22*2 + 33*3) ev. 154
Bibliografía
- Barnes, John. Ada 2012 Rationale: The language. Springer, 2013.
- Church, Alonzo. The Calculi of Lambda Conversion. Annals of Mathematicals Studies, No. 6. Princeton University Press, Princeton, New Jersey, 1941.
- Milner, R. A Proposal for Standard ML. ACM Symposium on Lisp and Functional Programming, 1984, pp. 184-197.
- Tennenbaum, A.M. Type determination in very high level languages. NSO-3, Courant Institute of Math. Sciences, Universidad de Nueva York, 1974.